/*
 * Copyright 2018- The Pixie Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

syntax = "proto3";

package px.api.vizierpb;

option go_package = "vizierpb";

import "github.com/gogo/protobuf/gogoproto/gogo.proto";

// The list of data types supported by our execution engine.
// Each type corresponds to a Column type below.
enum DataType {
  // Data has no specified type. Usually means there is an error.
  DATA_TYPE_UNKNOWN = 0;
  // Data is a bool.
  BOOLEAN = 1;
  // Data is a 64-bit integer.
  INT64 = 2;
  // Data is an unsigned 128 bit integer.
  UINT128 = 3;
  // Data is a 64-bit floating point.
  FLOAT64 = 4;
  // Data is a string message.
  STRING = 5;
  // Data is a 64-bit integer marking UNIX time in nanoseconds.
  TIME64NS = 6;
}

// The list of semantic types that can be output by the engine.
// These semantic types are not used in the execution engine. Instead
// they are created in the compiler and meant as clues to clients of Vizier how to format the
// data they receive. Specific semantic types are not explicitly tied
// to specific DataTypes but most are only used with a single DataType.
enum SemanticType {
  // Data has no specified Semantic Type. This usually is an error.
  ST_UNSPECIFIED = 0;
  // Data has no semantic type.
  ST_NONE = 1;
  // Data is UNIX time in nanoseconds.
  ST_TIME_NS = 2;
  // Data is the Agent UUID.
  ST_AGENT_UID = 100;
  // Data is the agent-specific ID, a Vizier specific numbering for Pixie Agents based on
  // the order they registered to the rest of Vizier.
  ST_ASID = 101;
  // Data is the unique ID for a process. Used to distinguish processes from other PIDs based on the
  // time and Agent (ASID) that monitored that process.
  ST_UPID = 200;
  // Data is the name of a service.
  ST_SERVICE_NAME = 300;
  // Data is the name of a Kubernetes pod.
  ST_POD_NAME = 400;
  // Data is the phase of a Kubernetes pod.
  ST_POD_PHASE = 401;
  // Data is the status of a Kubernetes pod.
  ST_POD_STATUS = 402;
  // Data is the name of the node.
  ST_NODE_NAME = 500;
  // Data is the name of a container.
  ST_CONTAINER_NAME = 600;
  // Data is the state of a container.
  ST_CONTAINER_STATE = 601;
  // Data is the status of a container.
  ST_CONTAINER_STATUS = 602;
  // Data is the name of a namespace.
  ST_NAMESPACE_NAME = 700;
  // Data is in bytes.
  ST_BYTES = 800;
  // Data is a percent value, but has not been scaled.
  ST_PERCENT = 900;
  // Data is a duration in nanoseconds.
  ST_DURATION_NS = 901;
  // Data is in throughput per nanosecond (1/ns). Typically used for metrics like requests per
  // nanosecond.
  ST_THROUGHPUT_PER_NS = 902;
  // Data is in throughput of bytes per nanosecond. (bytes/ns). Used for metrics like bytes per
  // nanosecond.
  ST_THROUGHPUT_BYTES_PER_NS = 903;
  // Data is a quantiles object.
  ST_QUANTILES = 1000;
  // Data is a quantiles object over duration data (ST_DURATION_NS).
  ST_DURATION_NS_QUANTILES = 1001;
  // Data is an IP Address.
  ST_IP_ADDRESS = 1100;
  // Data is a network port.
  ST_PORT = 1200;
  // Data is an HTTP request method. ie GET, POST.
  ST_HTTP_REQ_METHOD = 1300;
  // Data is an HTTP response status. ie 200, 404, 500.
  ST_HTTP_RESP_STATUS = 1400;
  // Data is an HTTP response message itself.
  ST_HTTP_RESP_MESSAGE = 1500;
  // Data is a script reference. Mainly used in the UI.
  ST_SCRIPT_REFERENCE = 3000;
}

// The UInt128 data message.
message UInt128 {
  uint64 low = 1;
  uint64 high = 2;
}

// Boolean data column.
message BooleanColumn {
  repeated bool data = 1;
}

// Int64 column data.
message Int64Column {
  repeated int64 data = 1;
}

// Uint128 column data.
message UInt128Column {
  repeated UInt128 data = 1;
}

// Float64 column data.
message Float64Column {
  repeated double data = 1;
}

// Time64 column data.
message Time64NSColumn {
  repeated int64 data = 1;
}

// String data column.
message StringColumn {
  repeated bytes data = 1;
}

// A single column of data.
message Column {
  oneof col_data {
    BooleanColumn boolean_data = 1;
    Int64Column int64_data = 2;
    UInt128Column uint128_data = 3;
    Time64NSColumn time64ns_data = 4;
    Float64Column float64_data = 5;
    StringColumn string_data = 6;
  }
}

// RowBatchData contains the data for a particular row batch from the specified table.
message RowBatchData {
  // The ID of the table which the row batch belongs to.
  string table_id = 5 [ (gogoproto.customname) = "TableID" ];
  // The columns in this row-batch.
  repeated Column cols = 1;
  // The number of rows in this batch.
  int64 num_rows = 2;
  // Whether the row-batch marks the end of a window.
  bool eow = 3;
  // Whether the row-batch marks the end of a stream.
  bool eos = 4;
}

// Relation describes the structure of a table.
message Relation {
  message ColumnInfo {
    // The name of a column.
    string column_name = 1;
    // The basic data type of the column.
    DataType column_type = 2;
    // The description of the column.
    string column_desc = 3;
    // The semantic type of the column.
    SemanticType column_semantic_type = 4;
  }
  // The list of columns in this relation.
  repeated ColumnInfo columns = 1;
}

// A message for a single compiler error detail.
message CompilerError {
  // The line number in the pxl script where the compiler error occurred.
  uint64 line = 1;
  // The column number in the pxl script where the compiler error occurred.
  uint64 column = 2;
  // The message of this particular error.
  string message = 3;
}

// An individual error detail message.
message ErrorDetails {
  oneof error {
    CompilerError compiler_error = 1;
  }
}

// Message describing if something worked (code == 0) or why not.
message Status {
  // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].
  int32 code = 1;
  // A developer-facing error message, which should be in English. Any
  // user-facing error message should be localized and sent in the
  // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.
  string message = 2;
  // The details about the particular error. A Status can be an error status and have 0
  // elements in the error_details. However, if the Status.code is OK (Status.code == 0),
  // error_details should be empty as well.
  repeated ErrorDetails error_details = 4;
  reserved 3;
}

// This is a proto representation for common lifecycle states.
enum LifeCycleState {
  // State of the resource is unknown.
  UNKNOWN_STATE = 0;
  // Resource is still in the start up process.
  PENDING_STATE = 1;
  // Resource is active and healthy.
  RUNNING_STATE = 2;
  // Resource is not running and has failed.
  FAILED_STATE = 3;
  // Resource has been terminated with no errors.
  TERMINATED_STATE = 4;
}

// ScalarValues reference a single constant value.
message ScalarValue {
  // We need to store the type to handle the null case and make sure we have the
  // "correct" null value. This type takes precedence on the one of value below.
  // If they mismatch a null value will be used.
  DataType data_type = 1;
  oneof value {
    bool bool_value = 2;
    int64 int64_value = 3;
    double float64_value = 4;
    string string_value = 5;
    int64 time64_ns_value = 6;
    UInt128 uint128_value = 7;
  }
}

// Request for the ExecuteScript call. This
// should contain all necessary information to successfully run
// a script on Vizier.
message ExecuteScriptRequest {
  // query_str is the string representation of the query to run.
  string query_str = 1;
  // The UUID of the cluster encoded as a string with dashes.
  string cluster_id = 3 [ (gogoproto.customname) = "ClusterID" ];
  // FuncToExecute specifies the name and arguments of a function to execute.
  message FuncToExecute {
    // Name of function to execute. Can be of the form 'func_name' or 'module_name.func_name'.
    string func_name = 1;
    message ArgValue {
      // name of the argument.
      string name = 1;
      // value of the argument as a string.
      // Compiler will attempt to parse the string as the type expected.
      // eg. a value of "1" for a function expecting an int would parse to 1,
      // but the same value for a function expecting a string would parse to "1".
      // In the future, we could also support parsing of expressions here, but
      // this would require doing something along the lines of "'string literal'" for string.
      string value = 2;
    }
    // arg_values are the arguments to the function. If arg_values do not match
    // the signature of the function, then the ExecuteScriptResponse will return
    // an error stating this issue. Arg_values will not match if
    //   1. arg_values misses any parameters to `func_name`
    //   2. arg_values adds parameters to `func_name`.
    //   3. the value in arg_value doesn't parse to the expected type.
    repeated ArgValue arg_values = 2;
    // output_table_prefix is the prefix of the name of the table that is returned in the response.
    // If the function returns a dataframe, then the table name will be `format("%s",
    // output_table_prefix)`. If the function returns a list of dataframes (even if the list is of
    // length 1), then the table names will be `format("%s[%d]", output_table_prefix, index)`.
    string output_table_prefix = 3;
  }
  // exec_funcs is a list of functions to execute.
  // If any functions specified cannot be found ExecuteScriptResponse will contain a compiler error.
  repeated FuncToExecute exec_funcs = 4;
  // If set to true, the execute script will run the mutations (probe installs, etc.).
  // If unset, any mutation will be ignored.
  // If the mutated state is already ready, the script will also be executed.
  bool mutation = 5;

  message EncryptionOptions {
    // The encryption key in JWK format.
    string jwk_key = 1;
    // The algorithm used for the key.
    // https://datatracker.ietf.org/doc/html/rfc7518#section-4.1
    // Valid values: https://github.com/lestrrat-go/jwx/blob/main/jwa/key_encryption_gen.go
    string key_alg = 2;
    // The algorithm to use to encrpyt the content.
    // https://datatracker.ietf.org/doc/html/rfc7518#section-5.1
    // Valid values: https://github.com/lestrrat-go/jwx/blob/main/jwa/content_encryption_gen.go
    string content_alg = 3;
    // The algorithm to use to compress the content. A blank value indicates no compression.
    // https://datatracker.ietf.org/doc/html/rfc7516#section-4.1.3
    // Valid values: https://github.com/lestrrat-go/jwx/blob/main/jwa/compression_gen.go
    string compression_alg = 4;
  }
  // Options for encrypting the data.
  EncryptionOptions encryption_options = 6;

  // If query_id is passed in then this execute request is treated as
  // a resume request for an already existing query.
  string query_id = 7 [ (gogoproto.customname) = "QueryID" ];
  reserved 2;
  // Configs specifies extra configuration to be given to the compiler.
  Configs configs = 9;
  // Query name is used for labeling query execution timing metrics.
  string query_name = 10;
}

// Configs specifies extra configuration to be given to the compiler. For example,
// endpoint information that should be used across all OTel exports.
message Configs {
  // OTelEndpointConfig contains information about which OTelEndpoint + headers should be attached
  // to all OTel export calls in the script.
  message OTelEndpointConfig {
    string url = 1 [ (gogoproto.customname) = "URL" ];
    map<string, string> headers = 2;
    bool insecure = 3;
    int64 timeout = 4;
  }
  OTelEndpointConfig otel_endpoint_config = 1 [ (gogoproto.customname) = "OTelEndpointConfig" ];
  message PluginConfig {
    // The start_time of the script in nanoseconds.
    int64 start_time_ns = 1;
    // The end_time of the script in nanoseconds.
    int64 end_time_ns = 2;
  }
  PluginConfig plugin_config = 2;
}

// Tracks information about query execution time.
message QueryTimingInfo {
  // The total execution time for the query in nanoseconds.
  int64 execution_time_ns = 1;
  // The time in ns spent compiling the query.
  int64 compilation_time_ns = 2;
}

// QueryExecutionStats contains information about the time/data processed by the query.
// These will be periodically streamed from the server as the query executes.
message QueryExecutionStats {
  QueryTimingInfo timing = 1;
  // The number of input bytes.
  int64 bytes_processed = 2;
  // The number of input records.
  int64 records_processed = 3;
}

// The metadata describing a particular table that is sent over the stream.
// Metadata for a table is only sent once per stream and will be sent before
// data for that table is sent over the stream.
message QueryMetadata {
  // The relation for the table.
  Relation relation = 1;
  // The name of the table.
  string name = 2;
  // The UUID of the table. RowBatchData for this particular table will use
  // the same ID.
  string id = 3 [ (gogoproto.customname) = "ID" ];
}

// Data message containing either a row batch or execution stats.
message QueryData {
  // The batch of data to send over. The field encrypted_batch is used if encryption key is set.
  RowBatchData batch = 1;
  // If an encryption key is set, then the data will be sent over as an encrypted batch.
  bytes encrypted_batch = 3;
  // The execution stats to send over.
  QueryExecutionStats execution_stats = 2;
}

// Response to ExecuteScript call.
message ExecuteScriptResponse {
  // The Status for executing the query. Empty status implies that execution was successful/is
  // currently running/the mutation is still not ready. An non-empty status may indicate a compiler
  // error, execution error, timeout, etc.
  Status status = 1;
  // The id for the query. UUID encoded as string.
  string query_id = 2 [ (gogoproto.customname) = "QueryID" ];
  oneof result {
    QueryData data = 3;
    QueryMetadata meta_data = 4;
  }
  // The status of the mutation, only populated if the request was a mutation.
  MutationInfo mutation_info = 5;
}

// Status information for a muation.
message MutationInfo {
  message MutationState {
    // ID of resource created/updated by the mutation.
    string id = 1 [ (gogoproto.customname) = "ID" ];
    // State of resource created/updated by the mutation.
    LifeCycleState state = 2;
    // The name of the resource created/updated by the mutation.
    string name = 3;
  }
  // The overall status of the mutation. An UNAVAILABLE status means that the querybroker is still
  // waiting for some mutations to complete before the query can actually be executed.
  Status status = 1;
  // The states of the resources created/updated by the mutation.
  repeated MutationState states = 2;
}

// Request for the HealthCheck call.
message HealthCheckRequest {
  // The UUID of the cluster encoded as a string with dashes.
  string cluster_id = 1 [ (gogoproto.customname) = "ClusterID" ];
}

// Response for the HealthCheck call.
message HealthCheckResponse {
  // The status of the cluster.
  Status status = 1;
}

message GenerateOTelScriptRequest {
  string cluster_id = 1 [ (gogoproto.customname) = "ClusterID" ];
  string pxl_script = 2;
}

message GenerateOTelScriptResponse {
  // The Status of the request. Empty status implies that execution was successful
  // An non-empty status may indicate a compiler error, error while generating the otel script.
  Status status = 1;
  string otel_script = 2 [ (gogoproto.customname) = "OTelScript" ];
}

// The API that manages all communication with a particular Vizier cluster.
service VizierService {
  // Execute a script on the Vizier cluster and stream the results of that execution.
  rpc ExecuteScript(ExecuteScriptRequest) returns (stream ExecuteScriptResponse);
  // Start a stream to receive health updates from the Vizier service. For most practical
  // purposes, users should only need `ExecuteScript()` and can safely ignore this call.
  rpc HealthCheck(HealthCheckRequest) returns (stream HealthCheckResponse);
  // GenerateScript takes in a script that returns DataFrames and generates an OTel Export script
  // that sends the DataFrames over to an OpenTelemetry collector. If the input script does
  // not return a DataFrame, an error is returned.
  // If the generator is unable to export columns from any DataFrames, an error is returned.
  rpc GenerateOTelScript(GenerateOTelScriptRequest) returns (GenerateOTelScriptResponse);
}

message DebugLogRequest {
  // The UUID of the cluster encoded as a string with dashes.
  string cluster_id = 1 [ (gogoproto.customname) = "ClusterID" ];
  // The Vizier pod name we want to log.
  string pod_name = 2;
  // Return the log for the previous instance instead of the current active one.
  bool previous = 3;
  // The name of the container to get logs from. If none is specified and there is only a
  // single container on the pod, then that one is chosen.
  string container = 4;
}

message DebugLogResponse {
  // A chunk of the log response data.
  string data = 2;
}

enum ContainerState {
  CONTAINER_STATE_UNKNOWN = 0;
  CONTAINER_STATE_RUNNING = 1;
  CONTAINER_STATE_TERMINATED = 2;
  CONTAINER_STATE_WAITING = 3;
}

message ContainerStatus {
  string name = 1;
  ContainerState container_state = 2;
  string message = 3;
  string reason = 4;
  int64 start_timestamp_ns = 5 [ (gogoproto.customname) = "StartTimestampNS" ];
  int64 restart_count = 6;
}

enum PodPhase {
  PHASE_UNKNOWN = 0;
  PENDING = 1;
  RUNNING = 2;
  SUCCEEDED = 3;
  FAILED = 4;
}

message VizierPodStatus {
  // The name of the pod. Ex: vizier-pem-z26d8
  string name = 1;
  PodPhase phase = 2;
  string message = 3;
  string reason = 4;
  int64 created_at = 5;
  repeated ContainerStatus container_statuses = 6;
  int64 restart_count = 7;
}

message DebugPodsRequest {
  // The UUID of the cluster encoded as a string with dashes.
  string cluster_id = 1 [ (gogoproto.customname) = "ClusterID" ];
}

message DebugPodsResponse {
  // A list of the Vizier agent pods and their statuses.
  repeated VizierPodStatus data_plane_pods = 1;
  // A list of the Vizier control plane pods and their statuses.
  repeated VizierPodStatus control_plane_pods = 2;
}

// Service used to run debug commands on Vizier.
service VizierDebugService {
  // Get a debug log for a specific vizier pod.
  rpc DebugLog(DebugLogRequest) returns (stream DebugLogResponse);
  // Returns a list of Vizier pods and their statuses.
  rpc DebugPods(DebugPodsRequest) returns (stream DebugPodsResponse);
}
